/*
 * Copyright (c) 2007 Compl Yue Still, All Rights Reserved.
 */

/**
 * 
 */
package hbi.wire.java;

import static hbi.wire.java.DomainBoundary.HBI_VERSION;
import hbi.domain.DomainController;
import hbi.domain.java.JavaDomain;
import hbi.java.util.CharSetConstants;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

class PassiveHand implements WireHandshake
{

    private final DomainBoundary boundary;

    private final ByteBuffer localInfo;

    private final ByteBuffer remoteInfo;

    PassiveHand(DomainBoundary ap)
    {
        this.boundary = ap;

        this.localInfo = ByteBuffer.allocate(2000);
        localInfo.put(HBI_VERSION);
        localInfo.flip();

        this.remoteInfo = ByteBuffer.allocate(2000);
        // 3-bytes "HBI"
        // 2-bytes version
        // 2-bytes remote controller version
        // 2-bytes remote domain type name length in bytes of utf-8 encoding
        remoteInfo.limit(9);
    }

    int remoteControllerVersion;

    Class<? extends DomainController> remoteDomainType = null;

    int requestedLocalControllerVersion;

    JavaDomain localDomain = null;

    void readChannel(SelectionKey selKey) throws IOException
    {
        SocketChannel ch = (SocketChannel) selKey.channel();

        if (ch.read(remoteInfo) < 0)
        {
            // unexpected end of stream
            System.err.println("unexpected end of stream");
            ch.close();
            return;
        }

        if (remoteInfo.position() < remoteInfo.limit())
        // not fully read
            return;

        if (this.remoteDomainType != null)
        // remote domain type accepted, reading requested domain type name
        {
            if (remoteInfo.limit() == 4)
            // requested controller version and type name length ready
            {
                this.requestedLocalControllerVersion = remoteInfo.getShort(0);
                int requestedDomainTypeNameLength = remoteInfo.getShort(2);
                if (requestedDomainTypeNameLength < 1
                    || requestedDomainTypeNameLength > remoteInfo.capacity() - 4)
                {
                    // bad type name length
                    System.err.println("bad type name length");
                    ch.close();
                    return;
                }
                remoteInfo.limit(4 + requestedDomainTypeNameLength);
            }
            else
            // requestedDomainTypeName ready
            {
                String requestedDomainTypeName = new String(remoteInfo.array(),
                    4, remoteInfo.limit() - 4, "UTF-8");
                requestedDomainTypeName = requestedDomainTypeName.replace("::",
                    ".");
                selKey.interestOps(selKey.interestOps()
                    & (~SelectionKey.OP_READ));
                this.localDomain = this.boundary.resolveLocalDomain(
                    requestedDomainTypeName, requestedLocalControllerVersion);
                if (this.localDomain == null)
                // requested domain not available
                {
                    System.err.println("requested domain not available");
                    ch.close();
                }
                else
                {
                    localInfo.compact();
                    localInfo.putShort((short) localDomain
                        .getControllerVersion());
                    byte[] localDomainTypeName = localDomain
                        .getFormalDomainType().getCanonicalName().getBytes(
                            CharSetConstants.UTF8);
                    localInfo.putShort((short) localDomainTypeName.length);
                    localInfo.put(localDomainTypeName);
                    localInfo.flip();
                    selKey.interestOps(selKey.interestOps()
                        | (SelectionKey.OP_WRITE));
                }
            }
        }
        else if (remoteInfo.limit() == 9)
        // header ready
        {
            for (int i = 0; i < HBI_VERSION.length; i++)
                if (remoteInfo.get(i) != HBI_VERSION[i])
                {
                    // HBI & version not compatible
                    // enforce exact version match here
                    System.err.println("HBI & version not compatible");
                    ch.close();
                    return;
                }
            this.remoteControllerVersion = remoteInfo.getShort(5);
            int remoteDomainTypeNameLength = remoteInfo.getShort(7);
            if (remoteDomainTypeNameLength < 1
                || remoteDomainTypeNameLength > remoteInfo.capacity() - 9)
            {
                // bad type name length
                System.err.println("bad type name length");
                ch.close();
                return;
            }
            remoteInfo.limit(9 + remoteDomainTypeNameLength);
        }
        else
        // remote domain type name ready
        {
            String remoteDomainTypeName = new String(remoteInfo.array(), 9,
                remoteInfo.limit() - 9, "UTF-8");
            remoteDomainTypeName = remoteDomainTypeName.replace("::", ".");
            this.remoteDomainType = this.boundary.resolveRemoteDomainType(
                remoteDomainTypeName, remoteControllerVersion);
            if (this.remoteDomainType == null)
            {
                // unsupported remote domain
                System.err.println("unsupported remote domain: "
                    + remoteDomainTypeName);
                ch.close();
                return;
            }
            // leave out 2-bytes for requested controller version, and
            // 2-bytes for length of requested domain type name
            remoteInfo.clear();
            remoteInfo.limit(4);
        }
    }

    void writeChannel(SelectionKey selKey) throws IOException
    {
        SocketChannel ch = (SocketChannel) selKey.channel();
        ch.write(localInfo);
        if (!localInfo.hasRemaining())
            selKey.interestOps(selKey.interestOps() & (~SelectionKey.OP_WRITE));
    }

    boolean isFinished()
    {
        return remoteDomainType != null && localDomain != null
            && !localInfo.hasRemaining();
    }

    public JavaDomain getLocalDomain()
    {
        return this.localDomain;
    }

    public int getRemoteDomainControllerVersion()
    {
        return this.remoteControllerVersion;
    }

    public Class<? extends DomainController> getRemoteDomainType()
    {
        return this.remoteDomainType;
    }

    public int getRequestedLocalDomainControllerVersion()
    {
        return this.requestedLocalControllerVersion;
    }

}
